package com.nulldev.util.VariableAPI.util.random;

import java.io.File;
import java.security.Provider;
import java.security.Provider.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.nulldev.util.JVM.JVM;
import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.data.Variables;
import com.nulldev.util.io.IOUtilsOS;

import sun.security.jca.Providers;

public enum RandomAccelMethod {

	NO_ACCELERATION,

	/* nullUtil */

	FAST_RANDOM,

	/* FasterRandom */

	FASTER_RNG_TWISTER,
	FASTER_RNG_TAUS,
	FASTER_RNG_WELL512,
	FASTER_RNG_MRG32K3A,

	/* Platform Specific */

	/**
	 * TODO: Doesn't Java already use RDRAND and UNIX' RNG sources? Should this
	 * option even be a thing? <br>
	 * Apparently still faster than NO_ACCELERATION...ok.
	 */
	UNIX_RANDOM

	;

	private static boolean __isFile(final String string) {
		Variables.requireNonNull(string, "string");
		try {
			return new File(string).exists();
		} catch (Exception e) {
			return false;
		}
	}

	protected static boolean canUseSystemRNG() {
		if (IOUtilsOS.isWindows()) {
			return false;
		} else if (IOUtilsOS.isLinux() || IOUtilsOS.isBSD()) {
			return (__isFile("/dev/urandom") || __isFile("/dev/lrng"));
		} else {
			return false; // TODO
		}
	}

	protected static boolean hasRDRAND() {
		/*
		 * Technically Linux uses RDTSCP (Jitter) if RDRAND is unavailable, but it's
		 * still good enough imo
		 */
		return ArrayUtils.contains(RDRAND_ACCELERATED_ALGOS, getSecureRandomAlgorithm());
	}

	public final static boolean UNIX_RANDOM_ALLOWED = canUseSystemRNG();

	public static final RandomAccelMethod __get_accel_method() {
		return __get_accel_method(false);
	}

	public static final RandomAccelMethod __get_accel_method(final boolean preferAcceleration) {
		if (UNIX_RANDOM_ALLOWED && preferAcceleration)
			return RandomAccelMethod.UNIX_RANDOM;
		else if (preferAcceleration)
			return RandomAccelMethod.FASTER_RNG_WELL512;
		else if (preferAcceleration)
			return RandomAccelMethod.FAST_RANDOM;
		else
			return RandomAccelMethod.NO_ACCELERATION;
	}

	public static File __UNIX_RANDOM_GET_FILE() {
		if (!IOUtilsOS.isLinux() && !IOUtilsOS.isBSD())
			return null;
		if (__isFile("/dev/lrng"))
			return new File("/dev/lrng");
		return new File("/dev/urandom");
	}

	private static final String[] RDRAND_ACCELERATED_ALGOS =
		{ "NativePRNGNonBlocking", "NativePRNG", "NativePRNGBlocking" };
	private static final String[] WHITELISTED_ALGORITHMS =
		{ "NativePRNGNonBlocking", "Windows-PRNG", "NativePRNG", "SHA1PRNG" };

	public static String getSecureRandomAlgorithm() {
		if (JVM.version() <= 1.8f) {
			final List<String> supportedAlgos = new ArrayList<String>();
			for (final Provider p : Providers.getProviderList().providers()) {
				for (final Service s : p.getServices()) {
					if (s.getType().equals("SecureRandom")) {
						supportedAlgos.add(s.getAlgorithm());
					}
				}
			}
			final Optional<String> o = supportedAlgos.stream().filter(entry -> ArrayUtils.contains(WHITELISTED_ALGORITHMS, entry)).findFirst();
			if (o.isPresent()) {
				return o.get();
			} else {
				return null;
			}
		} else {
			/**
			 * Class com/nulldev/util/VariableAPI/util/random/RandomAccelMethod(unnamed
			 * module 0x0000000652743EB8) can not access class
			 * sun/security/jca/Providers(java.base) because module java.base does not
			 * export package sun/security/jca to module unnamed module 0x0000000652743EB8
			 * <br>
			 * YIPPIE!!!!111
			 */
			return null;
		}
	}
}
